﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Noria.Web.MyMSDN.Models;

namespace Noria.Web.MyMSDN.Workers
{
    internal class MsdnFileWorker
    {
        private static readonly log4net.ILog slog = log4net.LogManager.GetLogger("Noria.Spider");
        private static readonly log4net.ILog alog = log4net.LogManager.GetLogger("Noria.Audit");
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private static readonly string locales = GetAllLocales();

        private readonly string _url;
        private const int MaxPageSize = 100;

        public MsdnFileWorker(string url)
        {
            this._url = url;
        }

        public void RetrieveFileListByFamily(IStatistics stat, AsyncWaiter waiter, Family family)
        {
            var payload = GetPayload(stat, family, 0);

            // convert
            family.Files = new List<File>();
            PopulateFiles(stat, family, payload);

            // check total size
            var total = (int)payload.TotalResults;

            // paging...
            if (total > MaxPageSize)
            {
                int pages = total / MaxPageSize + 1;

                for (int i = 1; i < pages; i++)
                {
                    if (!waiter.IsAlive)
                        return;

                    payload = GetPayload(stat, family, i);
                    PopulateFiles(stat, family, payload);
                }
            }

            // validate total size
            if (family.Files.Count != total)
            {
                slog.ErrorFormat("Actual results {0} is different from total size {1} for family {2}.",
                                 family.Files.Count, total, family.Name);
                stat.AddError();
            }

            // log
            slog.InfoFormat("MSDN Files of Family ({0}) retrieved, len={1}", family.Name, family.Files.Count);
            foreach (var file in family.Files)
            {
                slog.DebugFormat("File -> Name: {0}, Id: {1}.",
                                 file.FileName, file.FileId);
            }
        }

        private dynamic GetPayload(IStatistics stat, Family family, int pageIndex)
        {
            var body = CreateSearchPayload(family.FamilyId, pageIndex);
            dynamic payload;

            try
            {
                payload = HttpBot.RequestJObject(stat, _url, Encoding.UTF8.GetBytes(body), "application/json");
            }
            catch (Exception e)
            {
                slog.Error("Failed to retrieve file from family " + family.Name, e);
                stat.AddError();
                throw;
            }

            return payload;
        }

        private void PopulateFiles(IStatistics stat, Family family, dynamic payload)
        {
            foreach (dynamic jFile in (IEnumerable)payload.Files)
            {
                try
                {
                    var file = new File
                        {
                            FileId = (long)jFile.FileId,
                            FileName = (string)jFile.FileName,
                            Description = (string)jFile.Description,
                            Sha1 = (string)jFile.Sha1Hash,
                            PostedDate = (DateTime)jFile.PostedDate,
                            //Locale = (jFile.LanguageCodes.Count > 1 ? "mu" : (string)jFile.LanguageCodes[0]),
                            DisplaySize = (string)jFile.Size,
                            FamilyId = family.FamilyId
                        };

                    if (jFile.LanguageCodes is JArray)
                    {
                        file.Locale = (jFile.LanguageCodes.Count > 1
                                           ? "mu"
                                           : (string)jFile.LanguageCodes[0]);
                    }
                    else
                    {
                        file.Locale = "na"; // not available......, refer file id=8477
                    }

                    family.Files.Add(file);
                }
                catch
                {
                    slog.ErrorFormat("Invalid content {0}", ((JObject)jFile).ToString());
                    stat.AddError();
                }
            }
        }

        private void MergeFile(IStatistics stat, IEnumerable<File> memory, IEnumerable<File> db, DbSet<File> context)
        {
            foreach (var file in memory)
            {
                var dbFile = db.FirstOrDefault(p => p.FileId == file.FileId);
                if (dbFile != null)
                {
                    bool updated = false;

                    if (!dbFile.Enabled)
                    {
                        // update
                        dbFile.Enabled = true;
                        updated = true;
                    }

                    // NOTE: do not update family FK since in reality, family maps file with *.*
                    //
                    //if (dbFile.FamilyId != file.FamilyId)
                    //{
                    //    // update
                    //    dbFile.FamilyId = file.FamilyId;
                    //    updated = true;
                    //}

                    if (dbFile.FileName != file.FileName)
                    {
                        // update
                        dbFile.FileName = file.FileName;
                        updated = true;
                    }

                    if (dbFile.Sha1 != file.Sha1)
                    {
                        // update
                        dbFile.Sha1 = file.Sha1;
                        updated = true;
                    }

                    if (dbFile.DisplaySize != file.DisplaySize)
                    {
                        // update
                        dbFile.DisplaySize = file.DisplaySize;
                        updated = true;
                    }

                    if (dbFile.Description != file.Description)
                    {
                        // update
                        dbFile.Description = file.Description;
                        updated = true;
                    }

                    if (dbFile.Locale != file.Locale)
                    {
                        // update
                        dbFile.Locale = file.Locale;
                        updated = true;
                    }

                    if (updated)
                    {
                        stat.AddUpdated();
                    }
                }
                else
                {
                    // insert
                    file.Enabled = true;
                    context.Add(file);
                    stat.AddInserted();
                }
            }

            foreach (var dbFile in db)
            {
                if (memory.All(p => p.FileId != dbFile.FileId))
                {
                    // delete
                    if (dbFile.Enabled)
                    {
                        dbFile.Enabled = false;
                        stat.AddDeleted();
                    }
                }
            }
        }

        public void Save(IStatistics stat, IEnumerable<File> files)
        {
            // diff with db, update if necessary
            using (var db = new MmContext())
            {
                // get all db files
                //var dbFiles = db.Files.ToArray();

                log.Debug("Before MergeFile...");
                //MergeFile(stat, files, dbFiles, db.Files);

                // refer http://stackoverflow.com/questions/4355474/how-do-i-speed-up-dbset-add
                db.Configuration.ValidateOnSaveEnabled = false;
                db.Configuration.AutoDetectChangesEnabled = false;
                //
                foreach (var file in files)
                {
                    db.Files.Add(file);
                }
                log.Debug("After MergeFile...");


                log.Debug("Before db.SaveChanges...");
                int ret = db.SaveChanges();
                log.Debug("After db.SaveChanges...");


                alog.InfoFormat("File updated, count={0}", ret);
            }
        }

        private string CreateSearchPayload(long familyId, int page)
        {
            dynamic payload = new JObject();

            payload.Languages = locales;
            payload.Architectures = "";
            payload.ProductFamilyIds = "";
            payload.FileExtensions = "";
            payload.MyProducts = false;
            payload.ProductFamilyId = familyId;
            payload.SearchTerm = "";
            payload.Brand = "MSDN";
            payload.PageIndex = page;
            payload.PageSize = MaxPageSize;
            payload.FileId = 0;

            return ((JObject)payload).ToString(Formatting.None);
        }

        private static string GetAllLocales()
        {
            using (var db = new MmContext())
            {
                return string.Join(",", db.Locales.Where(p => p.Enabled).Select(p => p.Code).ToArray());
            }
        }

    }
}